本文同步更新於blog
客戶不應該依賴他們不使用的方法。
透過將龐大的介面,拆分成一個個的小介面,
解開耦合,進而容易重構、修改及部署。
前陣子Johnny提出ISP的目的,是為了多型的實作,也是不錯的觀點。
(註:小介面比較易於實作)
Def. 全自動咖啡機:會自己磨粉、沖煮的咖啡機。
假使我們實作一個咖啡機介面,
內有兩種方法,分別是磨粉及沖煮。
隨著對咖啡的興趣增加,我們可能會想玩手沖壺。
這時會發現,原本咖啡機介面職責有點多。
手沖壺沒辦法實作咖啡機介面(不會磨粉)。
透過將介面拆分成:磨粉介面、沖煮介面,
我們可以讓原本的全自動咖啡機實作兩種介面。
而手沖壺實作沖煮介面。
這下原本的煮咖啡程式,就可以寫好不修改了。
以下提供範例程式碼:
情境:目前我們有一台全自動咖啡機
<?php
namespace App\SOLID\ISP\CoffeeMachine\Contracts;
interface AutomaticCoffeeMachineInterface
{
public function grind($coffeeBeans);
public function brew($coffeePowder);
}
<?php
namespace App\SOLID\ISP\CoffeeMachine;
use App\SOLID\ISP\CoffeeMachine\Contracts\AutomaticCoffeeMachineInterface;
class AutomaticCoffeeMachine implements AutomaticCoffeeMachineInterface
{
public function grind($coffeeBeans)
{
if ($coffeeBeans == '咖啡豆') {
return '咖啡粉';
}
}
public function brew($coffeePowder)
{
if ($coffeePowder == '咖啡粉') {
return '咖啡';
}
}
}
<?php
namespace App\SOLID\ISP\CoffeeMachine;
class Program
{
protected $coffeeGrinder;
protected $coffeeMaker;
public function getCoffeeByAutomaticCoffeeMachine($coffeeBeans)
{
$automaticCoffeeMachine = new AutomaticCoffeeMachine();
$this->coffeeGrinder = $automaticCoffeeMachine;
$this->coffeeMaker = $automaticCoffeeMachine;
return $this->getCoffee($coffeeBeans);
}
private function grind($coffeeBeans)
{
return $this->coffeeGrinder->grind($coffeeBeans);
}
private function brew($coffeePowder)
{
return $this->coffeeMaker->brew($coffeePowder);
}
private function getCoffee($coffeeBeans)
{
$coffeePowder = $this->grind($coffeeBeans);
$coffee = $this->brew($coffeePowder);
return $coffee;
}
}
然而,隨著對咖啡興趣增加,我們想來玩玩摩卡壺。
卻發現摩卡壺不會磨粉...
需求一:將全自動咖啡機介面職責分離,拆分出磨粉介面與沖煮介面
<?php
namespace App\SOLID\ISP\CoffeeMachine\Contracts;
interface CoffeeGrinder
{
public function grind($coffeeBeans);
}
<?php
namespace App\SOLID\ISP\CoffeeMachine\Contracts;
interface CoffeeMaker
{
public function brew($coffeePowder);
}
<?php
namespace App\SOLID\ISP\CoffeeMachine;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeGrinder;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeMaker;
class AutomaticCoffeeMachine implements CoffeeGrinder, CoffeeMaker
{
public function grind($coffeeBeans)
{
if ($coffeeBeans == '咖啡豆') {
return '咖啡粉';
}
}
public function brew($coffeePowder)
{
if ($coffeePowder == '咖啡粉') {
return '咖啡';
}
}
}
需求二:實作磨豆機與摩卡壺
<?php
namespace App\SOLID\ISP\CoffeeMachine;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeGrinder;
class NormalCoffeeGrinder implements CoffeeGrinder
{
public function grind($coffeeBeans)
{
if ($coffeeBeans == '咖啡豆') {
return '咖啡粉';
}
}
}
<?php
namespace App\SOLID\ISP\CoffeeMachine;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeMaker;
class Mocalpot implements CoffeeMaker
{
public function brew($coffeePowder)
{
if ($coffeePowder == '咖啡粉') {
return '咖啡';
}
}
}
<?php
namespace App\SOLID\ISP\CoffeeMachine;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeGrinder;
use App\SOLID\ISP\CoffeeMachine\Contracts\CoffeeMaker;
use App\SOLID\ISP\CoffeeMachine\NormalCoffeeGrinder;
use App\SOLID\ISP\CoffeeMachine\MocalPot;
class Program
{
/**
* @var CoffeeGrinder
*/
protected $coffeeGrinder;
/**
* @var CoffeeMaker
*/
protected $coffeeMaker;
public function getCoffeeByAutomaticCoffeeMachine($coffeeBeans)
{
$automaticCoffeeMachine = new AutomaticCoffeeMachine();
$this->setCoffeeGrinder($automaticCoffeeMachine);
$this->setCoffeeMaker($automaticCoffeeMachine);
return $this->getCoffee($coffeeBeans);
}
public function getCoffeeByNormalCoffeeGrinderAndMocalPot($coffeeBeans)
{
$normalCoffeeGrinder = new NormalCoffeeGrinder();
$mocalPot = new MocalPot();
$this->setCoffeeGrinder($normalCoffeeGrinder);
$this->setCoffeeMaker($mocalPot);
return $this->getCoffee($coffeeBeans);
}
private function grind($coffeeBeans)
{
return $this->coffeeGrinder->grind($coffeeBeans);
}
private function brew($coffeePowder)
{
return $this->coffeeMaker->brew($coffeePowder);
}
private function getCoffee($coffeeBeans)
{
$coffeePowder = $this->grind($coffeeBeans);
$coffee = $this->brew($coffeePowder);
return $coffee;
}
private function setCoffeeGrinder(CoffeeGrinder $coffeeGrinder)
{
$this->coffeeGrinder = $coffeeGrinder;
}
private function setCoffeeMaker(CoffeeMaker $coffeeMaker)
{
$this->coffeeMaker = $coffeeMaker;
}
}
透過介面隔離原則,拆分職責,我們就可以玩更多花樣的咖啡沖煮方法了。
而煮咖啡方法也不必因為器具更換而修改。
符合開放封閉原則。
ʕ •ᴥ•ʔ:ISP不就是介面版本的單一職責原則嗎?(笑)